#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#include "Eitoboru.h"
#include "CollisionSolving.h"

IrrlichtDevice       *g_Device = 0;
EventManager		 *g_EventManager = 0;
video::IVideoDriver  *g_Driver = 0;
scene::ISceneManager *g_Smgr = 0;

gui::IGUIEnvironment *g_Env = 0;

scene::ICameraSceneNode *g_Camera = 0;

scene::IAnimatedMesh *g_HallMesh = 0;
scene::ISceneNode    *g_Hall = 0;
scene::IAnimatedMesh *g_TableMesh = 0;
scene::ISceneNode    *g_Table = 0;
scene::IAnimatedMesh *g_CueMesh = 0;
scene::ISceneNode    *g_Cue = 0;
scene::IAnimatedMesh *g_BallMesh = 0;

Sphere               *g_Balls[16];
Sphere               *g_Pockets[6];

scene::ISceneNode    *g_TableSurfaceBox = 0;
scene::ISceneNode    *g_PlaneCollider = 0;

scene::IAnimatedMesh *g_LightMesh;
LightUnit			 *g_Light[2];

Plane                *g_Plane;

int LoadHall( void )
{
    // Load the room mesh
	g_HallMesh = g_Smgr->getMesh( "../assets/PoolHall.obj" );
    g_Hall = 0;

	if( g_HallMesh )
	{
		g_Hall = g_Smgr->addMeshSceneNode( g_HallMesh->getMesh( 0 ) );
        g_Hall->setMaterialFlag( video::EMF_LIGHTING, true );
		
#ifdef _DEBUG
        g_Hall->setDebugDataVisible( true );
#endif
	}
    else
    {
        perror( "Failed to instantiate the room mesh" );

        return 0;
    }

    return 1;
}


int LoadTable( void )
{
    // Load the g_Table mesh
    g_TableMesh = g_Smgr->getMesh( "../assets/Table.obj" );
    g_Table = 0;

    if( g_TableMesh )
    {
        g_Table = g_Smgr->addMeshSceneNode( g_TableMesh->getMesh( 0 ) );
        g_Table->setPosition( core::vector3df( 0.0f, -10.0f, 0.0f ) );
        g_Table->setMaterialFlag( video::EMF_LIGHTING, true );

		scene::ITriangleSelector *selector = g_Smgr->createTriangleSelector( g_TableMesh, g_Table );
		g_Table->setTriangleSelector(selector);
		selector->drop();

        g_TableSurfaceBox = g_Smgr->addCubeSceneNode( 1.0f );
        g_TableSurfaceBox->setScale( core::vector3df( 5.7f, 2.0f, 11.7f ) );
        g_TableSurfaceBox->setPosition( core::vector3df( 0.05f, -5.6f, -0.3f ) );
        g_TableSurfaceBox->setMaterialType( video::EMT_TRANSPARENT_ADD_COLOR );
        g_TableSurfaceBox->getMaterial( 0 ).AmbientColor = video::SColor( 255, 0, 0, 0 );

#ifdef _DEBUG
        g_TableSurfaceBox->setDebugDataVisible( true );
#endif
    }
    else
    {
        perror( "Failed to instantiate the table mesh" );

        return 0;
    }

    return 1;
}


int LoadCue( void )
{
    g_CueMesh = g_Smgr->getMesh( "../assets/Cue.obj" );
    g_Cue = 0;

    if( g_CueMesh )
    {
        g_Cue = g_Smgr->addMeshSceneNode( g_CueMesh->getMesh( 0 ) );
        g_Cue->setPosition( core::vector3df( 1.0f, 1.0f, 1.0f ) );
        g_Cue->setRotation( core::vector3df( -90.0f, 90.0f, 0.0f ) );
        g_Cue->setParent( g_Camera );
#ifdef _DEBUG
        g_Cue->setDebugDataVisible( true );
#endif
    }
    else
    {
        perror( "Failed to instantiate the cue mesh" );

        return 0;
    }

    return 1;
}


int LoadPockets( void )
{
    if( g_BallMesh )
    {
        int j = 0;

        for( int i = 0 ; i < 2 ; ++i )
        {
            for( int n = 0 ; n < 3 ; ++n )
            {
                scene::ISceneNode *node = g_Smgr->addMeshSceneNode( g_BallMesh->getMesh( 0 ) );
                g_Pockets[j++] = new Sphere( node );

                node->setPosition( core::vector3df( 2.8f - i * 5.5f, -6.8f, 5.3f - n * 5.6f ) );
                node->setVisible( false );

#ifdef _DEBUG
                node->setDebugDataVisible( true );
#endif
            }
        }
    }
    else
    {
        perror( "Failed to instantiate the ball mesh" );

        return 0;
    }

    return 1;
}


int LoadBalls( void )
{
    g_BallMesh = g_Smgr->getMesh( "../assets/Ball.obj" );
    
    if( g_BallMesh )
    {
        int n = 0;

        // Little racking algorithm
        for( int i = 0 ; i < 6 ; ++i )
        {
            for( int j = 0 ; j < i ; ++j )
            {
                int x = (j - 1) * 5 - (i - 1) * 5;
                int y = int( sqrt( 3.0f ) * 0.5f * i );

                scene::ISceneNode *node = g_Smgr->addMeshSceneNode( g_BallMesh->getMesh( 0 ) );
				scene::ITriangleSelector *selector = g_Smgr->createTriangleSelector( g_BallMesh, node );
				node->setTriangleSelector(selector);
				selector->drop();
                g_Balls[n] = new Sphere( node );
                node->setPosition( core::vector3df( -0.6f - 0.05f * x, 0.0f, 1.8f + 0.3f * y ) );

                if( n == 0 )
                {
                    g_Balls[n]->SetVelocity( core::vector3df( 0.0f, 0.0f, 0.3f ) );
                }

        #ifdef _DEBUG
                node->setDebugDataVisible( true );
        #endif

                ++n;
            }
        }
    }
    else
    {
        perror( "Failed to instantiate the ball mesh" );

        return 0;
    }

    return 1;
}

int LoadLight( void )
{
	g_LightMesh = g_Smgr->getMesh( "../assets/Light2.obj" );

    if( g_LightMesh )
    {
		for( int i = 0; i < 2; i++ )
		{
			g_Light[i] = new LightUnit( g_Smgr->addMeshSceneNode( g_LightMesh->getMesh( 0 ) ), g_Smgr->addLightSceneNode( 0, core::vector3df( 0, 0, 0), video::SColorf(1,1,1,1), 5, 0 ) );
		}

		g_Light[0]->setPosition( core::vector3df( 5.0f, 5.75f, -0.3f ) );
		g_Light[1]->setPosition( core::vector3df( -5.0f, 5.75f, -0.3f ) );
    }
    else
    {
        perror( "Failed to instantiate the light mesh" );

        return 0;
    }

    return 1;
}

int LoadPlaneCollider( void )
{
    scene::IMesh *p = g_Smgr->getGeometryCreator()->createPlaneMesh( core::dimension2d<f32>( 1.94, 1.94 * (3.0 / 4.0) ), core::dimension2d<u32>( 1, 1 ), 0, core::dimension2d<f32>( 1, 1 ) );
    g_PlaneCollider = g_Smgr->addMeshSceneNode( p );
    g_PlaneCollider->setPosition( core::vector3df( 0.0f, -6.1f, 1.5f ) );
    g_PlaneCollider->setRotation( core::vector3df( -45.0f, 0.0, 0.0f ) );

    g_Plane = new Plane( g_PlaneCollider );

    return 1;
}

int Init( void )
{
    // Lighting 
    g_Smgr->addLightSceneNode( 0, core::vector3df( 0, 0, 0 ), video::SColorf( 1.0f, 1.0f, 1.0f, 0.0f ), 10.0f );
    g_Smgr->setAmbientLight( video::SColor( 0, 70, 70, 70 ) );

    // Model loading
    if( !LoadHall() || !LoadCue() || !LoadTable() || !LoadBalls() || !LoadLight() || !LoadPockets() || !LoadPlaneCollider() )
    {
        return 0;
    }

    return 1;
}


void Update( void )
{
    // g_Cue placement
	core::triangle3df triangle;
	core::vector3df point;
	core::line3df ray = g_Smgr->getSceneCollisionManager()->getRayFromScreenCoordinates( core::vector2di( 512, 355 ) , g_Camera );
	scene::ISceneNode *node = g_Smgr->getSceneCollisionManager()->getSceneNodeAndCollisionPointFromRay(ray, point, triangle, 0, 0, false);

    if( node == g_Table )
    {
		Rotatecue( true );
    }
    else
    {
		Rotatecue( false );
    }

	for(unsigned int i = 0; i < 15; ++i)
	{
		if( node == g_Balls[i]->GetNode() )
		{
			Rotatecue( true );
		}
	}

    // Hit the lights by collision
	for( int i = 0; i < 2; i++ )
	{
		if( node && node == g_Light[i]->getNode() )
		{
			g_Light[i]->SmashLight( core::vector3df( 0, 0, 1 ) );
		}
	}

	// Hit the lights by button
	if( g_EventManager->getKey( '1' ) )
	{
		g_Light[0]->SmashLight( core::vector3df( 0, 0, 1 ) );
	}
	if( g_EventManager->getKey( '2' ) )
	{
		g_Light[1]->SmashLight( core::vector3df( 0, 0, 1 ) );
	}

    // Update all balls
    for( int i = 0 ; i < 15 ; ++i )
    {
        if( g_Balls[i] )
        {
            g_Balls[i]->Update();
        }
    }

    static unsigned int last = 0;
    unsigned int time;

    // Make sure to run at a constant rate
    if( (time = g_Device->getTimer()->getTime()) > last + 10 ) 
    {
        last = time;

		// Update light
		for( int i = 0; i < 2; i++)
		{
			g_Light[i]->Update();
		}

        // Do collision solving
        CollisionSolving::Solve( time + 10 );
    }

	//Check for mouseclick
	ShootListener();

	//Solve collision with table
	CollisionSolving::PreventTableWalkthrough();
}

void ShootListener( void )
{
	if(g_EventManager->getMouseKey(0))
	{
		core::triangle3df triangle;
		core::vector3df point;
		core::line3df ray = g_Smgr->getSceneCollisionManager()->getRayFromScreenCoordinates( core::vector2di( 512, 355 ) , g_Camera );
		scene::ISceneNode *collisionNode = g_Smgr->getSceneCollisionManager()->getSceneNodeAndCollisionPointFromRay(ray, point, triangle, 0, 0, false);


		if(collisionNode)
		{	
			core::vector3df angle = collisionNode->getAbsolutePosition() - g_Camera->getAbsolutePosition();
			angle.Y = 0;
			angle.normalize();
			for(unsigned int i = 0; i < 15; ++i)
			{
				if(collisionNode == g_Balls[i]->GetNode())
				{
					g_Balls[i]->SetVelocity(angle * 0.2);
				}
			}
		}
	}
}

void Rotatecue( bool enabled )
{
	if( enabled == true )
    {
#ifdef _DEBUG
        g_Table->setDebugDataVisible( true );
#endif

        g_Cue->setRotation( core::vector3df( 0.0f, 0.0f, 0.0f ) );
        g_Cue->setPosition( core::vector3df( 0.0f, -0.5f, 0.0f ) );
    }
    else
    {
#ifdef _DEBUG
        g_Table->setDebugDataVisible( false );
#endif

        g_Cue->setRotation( core::vector3df( -90.0f, 90.0f, 0.0f ) );
        g_Cue->setPosition( core::vector3df( 1.0f, 1.0f, 1.0f ) );
    }
}

void Cleanup( void )
{
    g_Camera->drop(); g_Camera = 0;

    g_PlaneCollider->drop(); g_PlaneCollider = 0;
    g_HallMesh->drop(); g_HallMesh = 0;
    g_Hall->drop(); g_Hall = 0;
    g_TableMesh->drop(); g_TableMesh = 0;
    g_Table->drop(); g_Table = 0;
    g_CueMesh->drop(); g_CueMesh = 0;
    g_Cue->drop(); g_Cue = 0;
    g_BallMesh->drop(); g_BallMesh = 0;

    delete g_Plane;

    for( int i = 0 ; i < 16 ; ++i )
    {
        delete g_Balls[i];
        g_Balls[i] = 0;
    }
    for( int i = 0 ; i < 6 ; ++i )
    {
        delete g_Pockets[i];
        g_Pockets[i] = 0;
    }

    g_TableSurfaceBox->drop(); g_TableSurfaceBox = 0;

    g_LightMesh->drop(); g_LightMesh = 0;

    for( int i = 0 ; i < 2 ; ++i )
    {
        delete g_Light[i];
        g_Light[i] = 0;
    }

    delete g_EventManager; g_EventManager = 0;

    g_Device->drop();
}


int main( void )
{
	g_EventManager = new EventManager(g_Camera);
	SIrrlichtCreationParameters *creationParam = new SIrrlichtCreationParameters();
	creationParam->AntiAlias  = 16;
	creationParam->DriverType = video::EDT_OPENGL;
	creationParam->WindowSize = core::dimension2d<u32>( 1024, 600 );
    creationParam->Vsync      = true;
	
	g_Device = createDeviceEx( *creationParam );

	if( g_Device == 0 )
    {
        perror( "Failed to create device" );

		return 1;
    }

	g_Driver = g_Device->getVideoDriver();
	g_Smgr   = g_Device->getSceneManager();
	g_Env    = g_Device->getGUIEnvironment();

    // Enable 32-bits textures for parallax mapping
	g_Driver->setTextureCreationFlag( video::ETCF_ALWAYS_32_BIT, true );

    // Initialize keymap
    SKeyMap keyMapArray[5];
    keyMapArray[0].Action  = EKA_MOVE_FORWARD;  keyMapArray[0].KeyCode = irr::KEY_KEY_W;
    keyMapArray[1].Action  = EKA_MOVE_BACKWARD; keyMapArray[1].KeyCode = irr::KEY_KEY_S;
    keyMapArray[2].Action  = EKA_STRAFE_LEFT;   keyMapArray[2].KeyCode = irr::KEY_KEY_A;
    keyMapArray[3].Action  = EKA_STRAFE_RIGHT;  keyMapArray[3].KeyCode = irr::KEY_KEY_D;
    keyMapArray[4].Action  = EKA_JUMP_UP;       keyMapArray[4].KeyCode = irr::KEY_SPACE;

    // Add a camera
	g_Camera = g_Smgr->addCameraSceneNodeFPS(
        0,
        35.0f,
        0.01f,
        -1,
        keyMapArray,
        5,
        true
    );
	g_Camera->setPosition( core::vector3df( 0, -5, -8 ) );

    // Disable the mouse pointer
	g_Device->getCursorControl()->setVisible( false );

    if( !Init() )
    {
        Cleanup();

        return 1;
    }

    int lastFPS = -1;

    // Draw
	while( g_Device->run() )
    {
	    if( g_Device->isWindowActive() )
	    {
            // Clear the z and backbuffer, reset to black
		    g_Driver->beginScene( true, true, 0 );

		        g_Smgr->drawAll();
		        g_Env->drawAll();
				g_Smgr->setActiveCamera(g_Camera);
                Update();

		    g_Driver->endScene();

		    int fps = g_Driver->getFPS();

		    if( lastFPS != fps )
		    {
			    core::stringw str = L"Eitoboru Pool ";
                str += VER;
                str += " [";
			    str += g_Driver->getName();
			    str += "] FPS:";
			    str += fps;

			    g_Device->setWindowCaption( str.c_str() );
			    lastFPS = fps;
		    }
	    }
    }

	Cleanup();

	delete g_EventManager;
    delete creationParam;

#ifdef _DEBUG
        _CrtDumpMemoryLeaks();
#endif

	return 0;
}


